package com.gin.utils;

import com.alibaba.fastjson.JSONObject;
import com.gin.pojo.graph.GrEdge;
import com.gin.pojo.graph.GrNode;
import com.gin.pojo.graph.Graph;
import com.gin.pojo.graph.StackNode;
import com.vesoft.nebula.client.graph.data.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * 结果集基础类型处理的工具类
 *
 * @author zengxiang
 * <br>
 * Now is history
 */
public class ResultSetUtil {

    private static Logger log = LoggerFactory.getLogger(ResultSetUtil.class);

    /**
     * 生成自定义子图
     *
     * @param result nebula查询的子图结果集
     * @param vid    根节点唯一编号
     */
    public static Graph parseResultToGraph(ResultSet result, Long vid) {

        //查询结果集校验
        if (result == null) {
            return null;
        }
        if (!result.isSucceeded()) {
            log.warn("ResultSet errorCode ={}", result.getErrorCode());
            return null;
        }
        if (result.rowsSize() == 0) {
            return null;
        }

        //生成自定义子图
        Graph graph = new Graph();
        for (int i = 0; i < result.rowsSize(); i++) {
            ResultSet.Record record = result.rowValues(i);
            if (record == null || record.values() == null) {
                continue;
            }

            for (int j = 0; j < record.values().size(); j++) {
                ValueWrapper valueWrapper = record.values().get(j);
                ResultSetUtil.getValue(valueWrapper, graph);
            }
        }

        //指定根节点
        graph.rootNode = graph.nodes.get(vid);
        log.error("graph={}", JSONObject.toJSONString(graph));
        graphDFS(graph.rootNode);
        return graph;

    }


    /**
     * <p>从<b>根节点(rootNode)</b>出发，进行深度优先遍历(Depth First Search)</p>
     * <p>trail类型的路径由有限的边序列构成。遍历时只有点可以重复，边不可以重复。</p>
     * <p>由于边不可以重复，所以该图包含有限个路径。</p>
     *
     * @param node 根节点(rootNode)
     */
    public static void graphDFS(GrNode node) {
        if (node == null) {
            return;
        }
        //栈, 实际记录的是深度遍历的路径
        Stack<StackNode> stack = new Stack<>();
        //保证宽度遍历不重复进入
        HashSet<Long> breadthSet = new HashSet<>();
        //收集子图所有路径
        ArrayList<ArrayList<Long>> pathArrList = new ArrayList<>();

        //先压入深度遍历的出发点
        LinkedHashMap<Long, GrEdge> rootPath = new LinkedHashMap<>();
        rootPath.put(node.vid, null);
        breadthSet.add(node.vid);
        stack.add(new StackNode(node, rootPath, breadthSet));
        System.out.println(node.vid);

        while (!stack.isEmpty()) {
            //弹出节点, 并获取节点是否具有下个节点
            StackNode cur = stack.pop();
            if (cur.node.nexts.size() == 0) {

                ArrayList<Long> onePath = new ArrayList<>();
                double tmp = 1.0D;
                for (GrEdge value : cur.path.values()) {
                    if (value == null) {
                        continue;
                    }
                    tmp = tmp * value.conprop / 100;
                    onePath.add(value.src.vid);
                }
                onePath.add(cur.node.vid);
                pathArrList.add(onePath);
                System.out.println("rate=" + tmp + "\t path:" + JSONObject.toJSONString(onePath));
            }
            for (GrNode next : cur.node.nexts) {
                //判断宽度遍历上next是否已经出现过
                if (cur.breadthSet.contains(next.vid)) {
                    continue;
                }
                cur.breadthSet.add(next.vid);
                //判断下个节点是否已经走过
                if (cur.node != next && !cur.path.containsKey(next.vid)) {
                    //没有走过则把当前节点和下个节点逐步压入
                    //即:栈中保留了整条从开始节点出发的深度路径
                    stack.push(cur);
                    LinkedHashMap<Long, GrEdge> tmpPath = new LinkedHashMap<>(cur.path);
                    tmpPath.put(cur.node.vid, cur.node.edges.get(next.vid));
                    stack.push(new StackNode(next, tmpPath, new HashSet<>()));
                    //System.out.println(next.vid);
                    //不再寻找宽度的next, 而是跳出, 去找next的next了
                    //宽度的next可以在一条深度走到路的尽头后, 通过栈弹出进行回退深度, 然后在while继续遍历宽度
                    break;
                } else {
                    ArrayList<Long> onePath = new ArrayList<>();
                    double tmp = 1.0D;
                    for (GrEdge value : cur.path.values()) {
                        if (value == null) {
                            continue;
                        }
                        tmp = tmp * value.conprop / 100;
                        onePath.add(value.src.vid);
                    }
                    onePath.add(cur.node.vid);
                    onePath.add(next.vid);
                    tmp = tmp * cur.node.edges.get(next.vid).conprop / 100;
                    pathArrList.add(onePath);
                    System.out.println("rate=" + tmp + "\t path:" + JSONObject.toJSONString(onePath));
                }
            }
        }
    }

    /**
     * 递归查询, 层次不深可以考虑
     * @param node 根节点(rootNode)
     * @param path 路径
     */
    public static void graphDFS2(GrNode node, HashSet<Long> path) {
        if (node == null) {
            return;
        }
        if (path.contains(node.vid)) {
            System.out.println(path);
            return;
        }
        path.add(node.vid);


        for (GrNode next : node.nexts) {
            //System.out.println(next.vid);
            HashSet<Long> tmp = new HashSet<>(path);
            graphDFS2(next, tmp);
        }

    }

    /**
     * <p>根据nebula graph本身的类型说明，获取对应的 java对象值。</p>
     *
     * @param value nebula graph 类型数据，（结果集的元素）
     * @param <T>   目标结果类型
     * @return
     */
    public static <T> T getValue(ValueWrapper value, Graph graph) {
        try {
            Object o = value.isVertex() ? transformVertex(value.asNode(), graph)
                    : value.isEdge() ? transformEdge(value.asRelationship(), graph)
                    : value.isList() ? transformList(value.asList(), graph)
                    : null;

            return (T) o;
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 将nebula node装换为graph的点
     *
     * @param node  nebula以utf-8解码对象 node
     * @param graph 自定义子图
     * @return 自定义node
     */
    private static Object transformVertex(Node node, Graph graph) {

        List<String> tagNames = node.tagNames();
        if (tagNames.size() != 1) {
            log.warn("Sorry there is no parse implements for multi tags node: {}", node);
            return node;
        }
        GrNode grNode = null;
        try {
            Long vid = node.getId().asLong();
            grNode = graph.nodes.get(vid);
            if (grNode == null) {
                grNode = new GrNode(vid);
            }

            HashMap<String, ValueWrapper> map = node.properties(tagNames.get(0));
            grNode.tid = map.get("tid") == null ? -1 : map.get("tid").asLong();
            grNode.tname = map.get("tname") == null ? "" : map.get("tname").asString();
            grNode.uniscid = map.get("uniscid") == null ? "" : map.get("uniscid").asString();
            graph.nodes.put(vid, grNode);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }


        return grNode;
    }

    /**
     * 将nebula relationship装换为graph的边
     *
     * @param relationship nebula以utf-8解码对象 relationship
     * @param graph        自定义子图
     * @return 自定义edge
     */
    private static Object transformEdge(Relationship relationship, Graph graph) {
        //边方向需要反转
        ValueWrapper srcVal = relationship.dstId();
        ValueWrapper dstVal = relationship.srcId();
        if (srcVal == null || dstVal == null) {
            return relationship;
        }
        Long srcId = null;
        Long dstId = null;
        HashMap<String, ValueWrapper> map = null;
        double conProp = 0.0D;
        String invType = null;
        String invTypeEn = null;
        try {
            srcId = srcVal.asLong();
            dstId = dstVal.asLong();
            map = relationship.properties();
            conProp = Double.parseDouble(map.get("conprop") == null ? "0" : map.get("conprop").asString());
            invType = map.get("invtype") == null ? "" : map.get("invtype").asString();
            invTypeEn = map.get("invtype_en") == null ? "" : map.get("invtype_en").asString();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        if (!graph.nodes.containsKey(srcId)) {
            graph.nodes.put(srcId, new GrNode(srcId));
        }
        if (!graph.nodes.containsKey(dstId)) {
            graph.nodes.put(dstId, new GrNode(dstId));
        }
        GrNode fromNode = graph.nodes.get(srcId);
        GrNode toNode = graph.nodes.get(dstId);
        fromNode.out++;
        toNode.in++;
        if ("RE".equals(invTypeEn)) {
            fromNode.nexts.addLast(toNode);
        } else {
            fromNode.nexts.addFirst(toNode);
        }

        GrEdge grEdge = new GrEdge(conProp, invType, invTypeEn, fromNode, toNode);
        fromNode.edges.put(toNode.vid, grEdge);

        return grEdge;
    }

    /**
     * 将nebula relationship装换为graph的边
     *
     * @param list  nebula以utf-8解码对象 list
     * @param graph 自定义子图
     * @return list中对象继续解码完成后, 解码对象收集为新list
     */
    private static Object transformList(ArrayList<ValueWrapper> list, Graph graph) {
        ArrayList<Object> parseList = new ArrayList<>();
        for (ValueWrapper val : list) {
            Object valParse = getValue(val, graph);
            parseList.add(valParse);
        }
        return parseList;
    }


}
